<template>
  <div class="graph-root">
    <div class="header">Pipeline</div>
    <!--  画布容器 可滚动  -->
    <div ref="graphContent" class="graph-cont-scroll">
      <!--   缩放   -->
      <zoom-control class="zoom-control" @scale="changeScale" />
      <!--   画布   -->
      <!-- 目前pipeline的状态显示先去掉 -->
      <!-- <div 
        v-if="nodeStatus && nodeStatus.includes('RUNNING')"
        class="imgTip"
        align="center"
      >
        <img
          alt=""
          src="@/assets/view/train-waiting.png"
          width="312"
          height="165"
        />
        <p>Pipeline正在执行中， 完成后可预览</p>
      </div>
      <div
        v-else-if="nodeStatus && nodeStatus.includes('FAIL')"
        class="imgTip"
        align="center"
      >
        <img
          alt=""
          src="@/assets/view/train-fail.png"
          width="312"
          height="165"
        />
        <p>Pipeline执行出错， 请先返回数据视图修改</p>
      </div> -->
      <div
        id="global-graph"
        ref="graph"
        class="graph dragging-to"
        :style="{
          width: `${graphSize.width - 6}px`,
          height: `${graphSize.height - 6}px`,
        }"
      >
        <!-- 算子 节点 -->
        <template v-for="(node, index) in operatorNodes">
          <!-- 节点 -->
          <div
            :key="'operator' + index"
            :class="{
              'node-selected': selectedNode
                ? selectedNode.id === node.id
                : false,
            }"
            class="node-capsule"
            :style="{
              left: `${calcXPositionByCol(node.data.position.col)}px`,
              top: `${calcYPositionByRow(node.data.position.row)}px`,
              width: `${nodeWidth}px`,
              height: `${nodeHeight}px`,
            }"
          >
            <!-- TODO 附属节点 'z-index': node.data.parametersTable ? 60 - node.data.parametersTable.index : 60-->
            <!-- 节点胶囊 -->
            <node-capsule
              :node="node"
              :zoom-scale="zoomScale"
              :node-height="nodeHeight"
              :node-width="nodeWidth"
              :grid-size-width="gridSizeWidth"
              :grid-size-height="gridSizeHeight"
              :difference-of-grid-width="differenceOfGridWidth"
              :difference-of-grid-height="differenceOfGridHeight"
              :mode="false"
              :menu-open-id="menuOpenNodeId"
              @node-event="nodeEmitEvent"
            >
            </node-capsule>

            <a-popover
              :visible="nodePopStatus && menuOpenNodeId === node.id"
              trigger="click"
              :overlay-style="{ width: '500px' }"
            >
              <template slot="content">
                <div
                  class="content four-scroll-y"
                  style="max-width: 500px; overflow: auto;"
                >
                  <!-- <dataset-preview-table
                    :head="tableColumns"
                    :data="tableData"
                    height="500"
                    data-type="table"
                  /> -->
                  <a-table
                    v-if="tableLoading || !tableData"
                    :loading="tableLoading"
                  ></a-table>
                  <table v-else class="m-table" style="table-layout: fixed;">
                    <tr>
                      <th
                        v-for="title in tableColumns"
                        :key="`column${title.name}`"
                        scope="col"
                        class="m-th"
                      >
                        <a-tooltip placement="top">
                          <template slot="title">
                            <span>{{ title.name }}</span>
                          </template>
                          <span>{{ title.name }}</span>
                        </a-tooltip>
                      </th>
                    </tr>
                    <tr
                      v-for="(data, index1) in tableData"
                      :key="`data${index1}`"
                    >
                      <td
                        v-for="column in tableColumns"
                        :key="`data${column.name}`"
                        class="m-td"
                      >
                        <a-tooltip
                          placement="topLeft"
                          :overlay-style="{ maxWidth: '600px' }"
                        >
                          <template slot="title">
                            <span>{{ data[column.name] }}</span>
                          </template>
                          <span>{{ data[column.name] }}</span>
                        </a-tooltip>
                      </td>
                    </tr>
                  </table>
                </div>
              </template>

              <a-button :ref="`btn${node.id}`" type="text" style="border: 0;">
              </a-button>
            </a-popover>
          </div>
        </template>

        <!-- 连接线 @menu-position="menuPositionEmit" -->
        <line-of-relational-connection
          v-for="edge in getEdgeInfo"
          :key="edge.key"
          :edge="edge"
          :node-height="nodeHeight"
          :node-width="nodeWidth"
          :difference-of-grid-width="differenceOfGridWidth"
          :difference-of-grid-height="differenceOfGridHeight"
          :highlight="(connectionLineHighlight.indexOf(`${edge.startNodeId}-${edge.endNodeId}`) > -1)"
          :is-visitor="true"
          :is-lock="true"
        />
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import Component from 'vue-class-component'
import { Vue, Prop, Watch } from 'vue-property-decorator'
// 缩放
import ZoomControl from '@/components/studio/data/graph-compoments/ZoomControl.vue'
// 连线
import LineOfRelationalConnection from '@/components/studio/data/graph-compoments/LineOfRelationalConnection.vue'
// 节点胶囊
// import NodeCapsule from '@/components/studio/data/graph-compoments/NodeCapsule.vue'
import DataViewStore, { EditActionArea } from '@/store/modules/dataview'
import VisualizationStore from '@/store/modules/visualization'
import NodeCapsule from '@/components/visualization/PreviewNodeCapsule.vue'
import DatasetPreviewTable from '@/components/data/DatasetPreviewTable.vue'

import { queryColumn } from '@/api/pipeline'

// 工具方法 getNodeById,
import { generateEdgeInfo } from '@/components/studio/data/util-pipeline'

import { Position, Size } from '@/components/studio/data/interface'

interface KeyValue {
  [key: string]: any
}

@Component({
  components: {
    ZoomControl,
    LineOfRelationalConnection,
    NodeCapsule,
    DatasetPreviewTable,
  },
})
export default class Pipeline extends Vue {
  /**
   * 常量尺寸
   */
  // 节点尺寸 （init 基础尺寸）
  private nodeHeight: number = 40
  private initNodeHeight: number = 40
  private nodeWidth: number = 58
  private initNodeWidth: number = 58

  // 网格尺寸 （init 基础尺寸）
  private gridSizeWidth: number = 168
  private initGridSizeWidth: number = 168
  private gridSizeHeight: number = 128
  private initGridSizeHeight: number = 128
  // 初始比例系数
  public zoomScale: number = 100
  // more icon menu 样式
  public moreHover: any = 0
  // 网格是否能放置节点
  private nodeMap: any = {}
  // 节点右键menu打开状态
  public menuOpenNodeId: number | null = null
  public tableLoading: boolean = false
  // 项目 ID
  @Prop({ type: Number, default: 0 })
  projectId!: number
  // pipelineId 沿用版本1
  @Prop({ type: Number, default: 0 })
  pipelineId!: number

  // 用于table列数的自适应
  private minLength = document.body.clientWidth - 285
  private colLength = 180

  public get isLock() {
    return DataViewStore.getLock
  }

  //  自定义算子 taskId
  public taskId: number = 0

  public get isVisitor() {
    return DataViewStore.isVisitor
  }

  public get previewPopDisplay(): string {
    return VisualizationStore.previewPopDisplay
  }

  @Watch('previewPopDisplay')
  public handleVisMode(newValue: string) {
    if (newValue === 'close') {
      this.nodePopStatus = false
    }
  }

  public mouseOver(node: any) {
    this.moreHover = node.id
  }
  public mounseLeave() {
    this.moreHover = 0
  }

  /**
   * 画布的实际节点
   */
  // 选择的节点
  public get selectedNode() {
    return DataViewStore.selectNode
  }
  // 节点 列表
  private get operatorNodes() {
    return DataViewStore.pipelineNodes
  }

  private nodeStatus: any = []
  // 获取节点后更新滚动高度
  @Watch('operatorNodes')
  public getScrollSize() {
    if (this.operatorNodes) {
      this.initGraphSizeByNodes()
    }
    this.nodeStatus = this.operatorNodes
      ?.filter((item: any) => item.type !== 3)
      ?.map((d: any) => d?.data?.lastStatus)
  }

  // 获取节点后更新滚动高度
  @Watch('pipelineId')
  public pipelineIdChange() {
    this.getAllNodes()
  }

  /**
   * 画布容器
   */
  // 画布容器 位置 初始化获取
  public containerPos: Position = { x: 0, y: 0 }
  // 画布容器 宽高  初始化获取
  public containerHeightWidth: Size = { width: 0, height: 0 }
  // 画布容器对象
  private graphContent: any = null
  /**
   * 画布
   */
  // 画布尺寸
  private graphSize: Size = { width: 0, height: 0 }
  // 画布
  private graphElement: any = null

  // 节点与网格 相对位置差  X
  public get differenceOfGridWidth() {
    return (this.gridSizeWidth - this.nodeWidth) / 2
  }
  // 节点与网格 相对位置差  Y
  public get differenceOfGridHeight() {
    return (this.gridSizeHeight - this.nodeHeight) / 2
  }

  public get actionArea() {
    return DataViewStore.ActionArea
  }

  private nodeMenuStatus: boolean = false // 所有节点的 menu(复制，禁用，删除) 是否也有展开
  private nodePopStatus: boolean = false // 节点详情显示
  private tableData: any = []
  private tableColumns: any = []
  /**
   * $emit 节点组件编辑menu
   */
  public async nodeEmitEvent(info: {
    type: string
    value: boolean
    nodeId: number
  }) {
    if (info.type === 'menu') {
      this.nodePopStatus = false
      this.nodeMenuStatus = info.value // 只记录状态 只能开启一个右键菜单
      this.menuOpenNodeId = info.value ? info.nodeId : null
    } else if (info.type === 'pop') {
      this.nodePopStatus = info.value
      VisualizationStore.changePreviewPopStatus('open')
      const parameters = {
        curPage: 1,
        pageSize: 5,
        id: info.nodeId,
        type: 'task',
        projectId: this.projectId,
      }
      this.tableLoading = true
      const response = await queryColumn({ data: parameters })
      this.tableLoading = false
      if (response.data.code === 100) {
        this.tableData = response.data.result?.data
        this.tableColumns = response.data.result?.head
        // this.tableColumns = []
        // response.data?.result?.head.forEach((element: any) => {
        //   this.tableColumns.push({
        //     title: element.name,
        //     key: element.name,
        //     dataIndex: element.name,
        //     slots: { title: 'customTitle' },
        //   })
        // })
      } else {
        this.$message.error(response.data.message)
      }
    }
  }

  // 关闭pop
  public closePop() {
    this.menuOpenNodeId = null
  }

  /**
   * vue 生命周期 beforeMount
   */
  public beforeMount() {
    this.updatePipeline()
  }

  /**
   * vue 生命周期 mounted
   */
  public mounted() {
    this.graphElement = this.$refs.graph as HTMLDivElement
    this.graphElement.addEventListener('click', this.cancelSelectNode)
    // 附加 框选
    this.initVariable()
    // 监听浏览器窗口变化
    window.addEventListener('resize', this.initVariable)
    const scrollGraph = this.$refs.graphContent as HTMLDivElement
    scrollGraph.addEventListener('scroll', this.closePop)
    DataViewStore.setActionArea(EditActionArea.PIPELINE)
  }

  /**
   * vue 生命周期 beforeDestroy
   */
  public beforeDestroy() {
    // 清空 store 节点
    DataViewStore.initPipelineNodes()
    // selectNode 置空
    DataViewStore.changeSelectNode(null)
    window.removeEventListener('resize', this.initVariable)
    this.graphElement.removeEventListener('click', this.cancelSelectNode)
    window.removeEventListener('scroll', this.closePop)
  }

  // ======================= pipeline 画布 公共方法  start =======================
  /**
   * 全局 更新 画布
   */
  public updatePipeline() {
    this.getAllNodes()
  }

  /**
   * 根据 projectId 和 pipelineId 获取当前project所有节点
   */
  public getAllNodes() {
    if (this.pipelineId != null && this.pipelineId !== 0) {
      const parameters = {
        id: this.projectId,
        pipelineId: this.pipelineId,
      }
      DataViewStore.queryPipelineNodes(parameters)
    }
  }

  /**
   * 初始化画布 位置 尺寸  containerPos  containerHeightWidth
   * 获取容器初始化尺寸， 设置画布初始尺寸
   */
  public initVariable() {
    try {
      // 获取 容器 位置和宽高
      const containerRect = (this.$refs
        .graphContent as HTMLDivElement).getBoundingClientRect()
      this.containerPos.x = containerRect.x
      this.containerPos.y = containerRect.y
      this.containerHeightWidth.width = containerRect.width
      this.containerHeightWidth.height = containerRect.height
      this.initGraphSizeByNodes()
    } catch (error) {
      console.log(error)
    }
  }

  /**
   * 根据 列 位置计算 节点 x 位置 -节点 css 等
   * @param col 列
   */
  private calcXPositionByCol(col: number) {
    return (col - 1) * this.gridSizeWidth + this.differenceOfGridWidth
  }

  /**
   * 根据 行 位置计算 节点 y 位置 -节点 css 等
   * @param row 行
   */
  private calcYPositionByRow(row: number) {
    // TODO 附属节点 data?: any
    return (row - 1) * this.gridSizeHeight + this.differenceOfGridHeight
  }

  /**
   * 根据所有节点的中最大 col，row 最大位置计算 画布尺寸
   */
  public initGraphSizeByNodes() {
    // 所有节点最大行和列
    const maxRow: number = Math.max(
      ...this.operatorNodes?.map((item) => {
        return item.data.position.row
      })
    )
    const maxCol: number = Math.max(
      ...this.operatorNodes?.map((item) => {
        return item.data.position.col
      })
    )
    // 根据最大行列 计算最大位置
    const xMaxPosition: number =
      this.calcXPositionByCol(maxCol) +
      this.nodeWidth +
      this.differenceOfGridWidth
    const yMaxPosition: number =
      this.calcYPositionByRow(maxRow) +
      this.nodeHeight +
      this.differenceOfGridHeight
    // 计算 画布尺寸， 最小为容器尺寸
    this.graphSize.width = Math.max(
      xMaxPosition,
      this.containerHeightWidth.width
    )
    this.graphSize.height = Math.max(
      yMaxPosition,
      this.containerHeightWidth.height
    )
  }

  /**
   * 取消选择的节点 , 等一些 字段初始化
   */
  public cancelSelectNode() {
    // 有 menu 的情况不清空 selectNode
    // 取消选择 节点
    this.menuOpenNodeId = null
    DataViewStore.changeSelectNode(null)
    DataViewStore.setVRPanelShow(false)
    // 清空高亮线
    this.connectionLineHighlight = []
  }

  /**
   * 关闭所有打开面板及效果
   */
  public closeAll() {
    DataViewStore.changeSelectNode(null)
    DataViewStore.setVRPanelShow(false)
    // 清空高亮线
    this.connectionLineHighlight = []
  }

  /**
   * 选择节点  胶囊 NodeCapsule.vue $emit
   * ($emit)
   * @param eventInfo
   */
  public selectNode(eventInfo: any) {
    // todo 已经选择得节点 取消选择吗还有 selectNode 问题
    const { node } = eventInfo
    if (node.type === 6) {
      return
    }
    DataViewStore.setShowColumnIndex(false)
    if (this.selectedNode && this.selectedNode.id === node.id) {
      // DataViewStore.changeSelectNode(null)
      DataViewStore.setVRPanelShow(false)
    } else {
      DataViewStore.changeSelectNode(node)
      DataViewStore.clearTableSelection()
      // DataViewStore.setVRPanelShow(true)
      DataViewStore.setTableData({
        // 切换节点时清空数据
        head: [],
        data: [],
      })
    }
  }

  // =======================  连线相关 start =======================
  // 连接线高亮 [startNodeId-endNodeId]
  private connectionLineHighlight: any = []

  /**
   * 根据节点对连接线信息渲染列表， 左右中点为连接点
   */
  public get getEdgeInfo() {
    // 方法传参
    const parameters: any = {
      nodeHeight: this.nodeHeight,
      nodeWidth: this.nodeWidth,
      gridSizeWidth: this.gridSizeWidth,
      gridSizeHeight: this.gridSizeHeight,
      differenceOfGridWidth: this.differenceOfGridWidth,
      differenceOfGridHeight: this.differenceOfGridHeight,
    }
    return generateEdgeInfo(this.operatorNodes, parameters)
  }

  /**
   * 右下角缩放控件
   * @param zoom 缩放系数
   */
  private changeScale(zoom: number) {
    this.zoomScale = zoom
    // 根据 初始尺寸进行 放大缩小
    this.nodeHeight = (this.initNodeHeight * zoom) / 100
    this.nodeWidth = (this.initNodeWidth * zoom) / 100
    this.gridSizeWidth = (this.initGridSizeWidth * zoom) / 100
    this.gridSizeHeight = (this.initGridSizeHeight * zoom) / 100
    this.initGraphSizeByNodes()
  }
}
</script>

<style lang="less" scoped>
@import '@/styles/scroll-bar';
/* pipeline.vue style 拆分 */
.graph-root {
  box-sizing: border-box;
  height: 30%;
  position: relative;
  // width: calc(100% - 280px);
  width: 100%;

  .header {
    background: #ededf4;
    color: #5d637e;
    font-size: 14px;
    height: 38px;
    padding: 9px 0;
    padding-left: 20px;
    width: 100%;
  }
}

/* 可滚动 */
.graph-cont-scroll {
  //background-color: #f0f2f5;
  background-color: #fff;
  box-sizing: border-box;
  height: calc(100% - 38px);
  overflow: auto;
  transition: all 0.3s;
  width: 100%;

  .imgTip {
    margin-top: 10%;

    p {
      color: #5d637e;
      font-size: 12px;
    }
  }

  &::-webkit-scrollbar {
    height: 6px;
    width: 6px;
  }

  &::-webkit-scrollbar-track {
    background: rgb(239, 239, 239);
    border-radius: 2px;
  }

  &::-webkit-scrollbar-thumb {
    background: #bfbfbf;
    border-radius: 6px;
  }

  &::-webkit-scrollbar-thumb:hover {
    background: #888;
  }

  &::-webkit-scrollbar-button {
    background-color: #f1f1f1;
    height: 6px;
    width: 6px;
  }

  &::-webkit-scrollbar-corner {
    background: transparent;
  }

  /* 画布 */
  .graph {
    box-sizing: border-box;
    overflow: hidden;
    position: relative;
    /* transition: all 0.3s; */

    .node.selected {
      border: 2px solid #6570ff;
    }
  }
}

/* 节点 胶囊 */
.node-capsule {
  align-items: center;
  background-color: #fff;
  //border: 2px solid #e9e9e9;
  // background: #e9e9e9;
  box-sizing: border-box;
  cursor: pointer;
  display: flex;
  // flex-direction: row;
  font-size: 0.9rem;
  justify-content: center;
  margin: 0 auto;
  position: absolute;
  text-align: center;
  /*transition-duration: 0.2s, 0.2s;*/
  /*transition-property: top, left;*/
  user-select: none;
  // z-index: 60;

  /* 节点多表折叠按钮 取消 */
  .node-hover-toggle-table {
    border-radius: 2px;
    display: none;
    height: 60%;
    width: 20px;

    &:hover {
      background-color: #f1f1f1;
    }
  }

  /* 插槽 按钮 */
  .after-add-node {
    background-color: #fff;
    border-radius: 7px;
    display: none;
    font-size: 16px;
    height: 16px;
    line-height: 16px;
    position: absolute;
    right: -10px;
    text-align: center;
    width: 16px;
  }

  &:hover {
    border: 2px solid #d9d9d9;
    box-shadow: 0 0 5px rgba(88, 98, 230, 0.2);

    .after-add-node {
      display: inline-block;
    }
  }

  .node-detail {
    box-sizing: border-box;
    cursor: auto;
    font-size: 14px;
    margin: 0;
    max-width: 500px;
    padding: 0;
    position: absolute;
    top: calc(-10% - 5px);
    user-select: text;
    white-space: normal;
    z-index: 63;

    .arrow {
      background: transparent;
      border-bottom-color: #fff;
      border-left-color: #fff;
      border-right-color: transparent;
      border-style: solid;
      border-top-color: transparent;
      border-width: 4.24264069px;
      // box-shadow: -3px 3px 7px rgb(0 0 0 / 7%);
      box-shadow: -3px 3px 7px rgba(88, 98, 230, 0.2);
      display: block;
      height: 8.48528137px;
      left: -4px;
      position: absolute;
      // top: 50%;
      top: 10%;
      transform: translateY(-50%) rotate(45deg);
      width: 8.48528137px;
    }

    .content {
      background-clip: padding-box;
      background-color: #fff;
      border-radius: 2px;
      overflow: auto;
      padding: 12px 16px;
    }
  }
}

/* 选择的节点 */
.node-selected {
  //border: 2px solid #c5c9ff;
  border: 2px solid #c5c9ff;
  box-shadow: 0 0 5px rgba(88, 98, 230, 0.2);
  // background: #c5c9ff;
  /*box-shadow: 0 0 1px 2px rgba(65, 109, 220, 0.1);*/
}

.m-table {
  border-collapse: collapse;
  border-color: #e8e8e8;
  font-size: 12px;
  line-height: 29px;
  text-align: center;
  width: 100%;

  th {
    background: #fafafa;
  }
}

.m-th {
  border: 1px solid #e8e8e8;
  line-height: 20px;
  overflow: hidden;
  padding: 5px;
  text-overflow: ellipsis;
  white-space: nowrap;
  width: 100px;
}

.m-td {
  border: 1px solid #e8e8e8;
  line-height: 20px;
  overflow: hidden;
  padding: 5px;
  text-overflow: ellipsis;
  white-space: nowrap;
  width: 100px;
}
</style>
